/***********************************************************************
 * BinTrotter : BitTorrent library/client for the Android operating system.
 *  
 * Copyright (C) 2009, Christopher David Stones (chris.stones@gmail.com)
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************/

package org.bintrotter.apeer;

import org.bintrotter.Main;
import org.bintrotter.apeer.Peer.State;

public class StateRunner {
	
	private org.bintrotter.apeer.receivestates.States	currentReceiveState;
	private org.bintrotter.apeer.sendstates.States 		currentSendState;
	
	private org.bintrotter.apeer.receivestates.States	lastReceiveState;
	private org.bintrotter.apeer.sendstates.States 		lastSendState;
	
	private org.bintrotter.apeer.sendstates.SendState	outIdle;
	private org.bintrotter.apeer.sendstates.SendState	outBitfield;
	private org.bintrotter.apeer.sendstates.SendState	outCancel;
	private org.bintrotter.apeer.sendstates.SendState	outChoke;
	private org.bintrotter.apeer.sendstates.SendState	outConnecting;
	private org.bintrotter.apeer.sendstates.SendState	outHandshake;
	private org.bintrotter.apeer.sendstates.SendState	outHave;
	private org.bintrotter.apeer.sendstates.SendState	outInterested;
	private org.bintrotter.apeer.sendstates.SendState	outKeepAlive;
	private org.bintrotter.apeer.sendstates.SendState	outNotInterested;
	private org.bintrotter.apeer.sendstates.SendState	outPiece;
	private org.bintrotter.apeer.sendstates.SendState	outRequest;
	private org.bintrotter.apeer.sendstates.SendState	outUnchoke;
	private org.bintrotter.apeer.sendstates.SendState	outWaitForHandshake;
	private org.bintrotter.apeer.sendstates.SendState	outShutdown;
	private org.bintrotter.apeer.sendstates.SendState	outDead;
	private org.bintrotter.apeer.sendstates.SendState	outPort;
	
	
	private org.bintrotter.apeer.receivestates.ReceiveState	inBitfield;
	private org.bintrotter.apeer.receivestates.ReceiveState	inChoke;
	private org.bintrotter.apeer.receivestates.ReceiveState	inConnecting;
	private org.bintrotter.apeer.receivestates.ReceiveState	inDead;
	private org.bintrotter.apeer.receivestates.ReceiveState	inHandShake;
	private org.bintrotter.apeer.receivestates.ReceiveState	inHave;
	private org.bintrotter.apeer.receivestates.ReceiveState	inInterested;
	private org.bintrotter.apeer.receivestates.ReceiveState	inMessageId;
	private org.bintrotter.apeer.receivestates.ReceiveState	inMessageLength;
	private org.bintrotter.apeer.receivestates.ReceiveState	inNotInterested;
	private org.bintrotter.apeer.receivestates.ReceiveState	inPieceIndex;
	private org.bintrotter.apeer.receivestates.ReceiveState	inPieceBegin;
	private org.bintrotter.apeer.receivestates.ReceiveState	inPieceBlock;
	private org.bintrotter.apeer.receivestates.ReceiveState	inPort;
	private org.bintrotter.apeer.receivestates.ReceiveState	inRequest;
	private org.bintrotter.apeer.receivestates.ReceiveState	inShutdown;
	private org.bintrotter.apeer.receivestates.ReceiveState	inUnchoke;
	private org.bintrotter.apeer.receivestates.ReceiveState	inCancel;

	
	public StateRunner(Peer peer) {
		
		currentSendState		= org.bintrotter.apeer.sendstates.States.CONNECTING;
		currentReceiveState		= org.bintrotter.apeer.receivestates.States.CONNECTING;
		
		lastReceiveState = null;
		lastSendState = null;
		
		outIdle		 			= new org.bintrotter.apeer.sendstates.Idle(peer);
		outBitfield 			= new org.bintrotter.apeer.sendstates.Bitfield(peer);
		outCancel 				= new org.bintrotter.apeer.sendstates.Cancel(peer);
		outChoke 				= new org.bintrotter.apeer.sendstates.Choke(peer);
		outConnecting 			= new org.bintrotter.apeer.sendstates.Connecting(peer);
		outHandshake 			= new org.bintrotter.apeer.sendstates.HandShake(peer);
		outHave 				= new org.bintrotter.apeer.sendstates.Have(peer);
		outInterested 			= new org.bintrotter.apeer.sendstates.Interested(peer);
		outKeepAlive 			= new org.bintrotter.apeer.sendstates.KeepAlive(peer);
		outNotInterested 		= new org.bintrotter.apeer.sendstates.NotInterested(peer);
		outPiece				= new org.bintrotter.apeer.sendstates.Piece(peer);
		outRequest	 			= new org.bintrotter.apeer.sendstates.Request(peer);
		outUnchoke 				= new org.bintrotter.apeer.sendstates.Unchoke(peer);
		outWaitForHandshake		= new org.bintrotter.apeer.sendstates.WaitForHandshake(peer);
		outShutdown 			= new org.bintrotter.apeer.sendstates.Shutdown(peer);
		outDead 				= new org.bintrotter.apeer.sendstates.Dead(peer);
		outPort					= new org.bintrotter.apeer.sendstates.Port(peer);
		
		inBitfield	 			= new org.bintrotter.apeer.receivestates.Bitfield(peer);
		inChoke	 				= new org.bintrotter.apeer.receivestates.Choke(peer);
		inConnecting 			= new org.bintrotter.apeer.receivestates.Connecting(peer);
		inDead 					= new org.bintrotter.apeer.receivestates.Dead(peer);
		inHandShake 			= new org.bintrotter.apeer.receivestates.HandShake(peer);
		inHave	 				= new org.bintrotter.apeer.receivestates.Have(peer);
		inInterested 			= new org.bintrotter.apeer.receivestates.Interested(peer);
		inMessageId 			= new org.bintrotter.apeer.receivestates.MessageId(peer);
		inMessageLength 		= new org.bintrotter.apeer.receivestates.MessageLength(peer);
		inNotInterested 		= new org.bintrotter.apeer.receivestates.NotInterested(peer);
		inPieceIndex			= new org.bintrotter.apeer.receivestates.PieceIndex(peer);
		inPieceBegin 			= new org.bintrotter.apeer.receivestates.PieceBegin(peer);
		inPieceBlock 			= new org.bintrotter.apeer.receivestates.PieceBlock(peer);
		inPort 					= new org.bintrotter.apeer.receivestates.Port(peer);
		inRequest 				= new org.bintrotter.apeer.receivestates.Request(peer);
		inShutdown 				= new org.bintrotter.apeer.receivestates.Shutdown(peer);
		inUnchoke 				= new org.bintrotter.apeer.receivestates.Unchoke(peer);
		inCancel				= new org.bintrotter.apeer.receivestates.Cancel(peer);
	}
	
	public org.bintrotter.apeer.receivestates.ReceiveState get(org.bintrotter.apeer.receivestates.States s) {
		
		switch(s) {
		
			case CONNECTING:		return inConnecting;
			case HANDSHAKE:			return inHandShake;
			case LENGTH:			return inMessageLength;
			case MSG_ID:			return inMessageId;
			case CHOKE:				return inChoke;
			case UNCHOKE:			return inUnchoke;
			case INTERESTED:		return inInterested;
			case NOT_INTERESTED:	return inNotInterested;
			case BITFIELD: 			return inBitfield;
			case HAVE:				return inHave;
			case REQUEST: 			return inRequest;
			case PIECE_INDEX:		return inPieceIndex;
			case PIECE_BEGIN:		return inPieceBegin;
			case PIECE_BLOCK:		return inPieceBlock;
			case CANCEL:			return inCancel;
			case PORT:				return inPort;
			case SHUTDOWN: 			return inShutdown;
			case DEAD:				return inDead;
		}
		return null;
	}
	
	public org.bintrotter.apeer.sendstates.SendState get(org.bintrotter.apeer.sendstates.States s) {
	
		switch(s) {
		
			case CONNECTING:				return outConnecting;
			case HANDSHAKE:					return outHandshake;
			case WAIT_FOR_HANDSHAKE:		return outWaitForHandshake;
			case IDLE:						return outIdle;
			case KEEP_ALIVE:				return outKeepAlive;
			case CHOKE:						return outChoke;
			case UNCHOKE:					return outUnchoke;
			case INTERESTED:				return outInterested;
			case NOT_INTERESTED:			return outNotInterested;
			case HAVE:						return outHave;
			case BITFIELD:					return outBitfield;
			case REQUEST:					return outRequest;
			case PIECE:						return outPiece;
			case CANCEL:					return outCancel;
			case PORT:						return outPort;
			case SHUTDOWN:					return outShutdown;
			case DEAD:						return outDead;
		}
		return null;
	}
	
	public State update() {
		
		org.bintrotter.apeer.sendstates.SendState		ss = get(currentSendState);
		org.bintrotter.apeer.receivestates.ReceiveState	rs = get(currentReceiveState);
		
		if(lastReceiveState != currentReceiveState) {
		//	Main.log.info("rx: " + currentReceiveState);
			rs.reset();
		}
		
		if(lastSendState != currentSendState) {
		//	Main.log.info("tx: " + currentSendState);
			ss.reset();
		}
		
		lastReceiveState 	= currentReceiveState;
		lastSendState		= currentSendState;
		
		currentReceiveState = rs.update();
		currentSendState 	= ss.update();	
		
		boolean rxDead 			= (currentReceiveState 	== org.bintrotter.apeer.receivestates.States.DEAD);
		boolean rxShutdown		= (currentReceiveState 	== org.bintrotter.apeer.receivestates.States.SHUTDOWN);
		boolean rxHandshake		= (currentReceiveState 	== org.bintrotter.apeer.receivestates.States.HANDSHAKE);
		boolean rxConnecting	= (currentReceiveState 	== org.bintrotter.apeer.receivestates.States.CONNECTING);
		boolean rxProtocol		= !rxDead && !rxShutdown && !rxHandshake && !rxConnecting;
		
		boolean txDead 			= (currentSendState		== org.bintrotter.apeer.sendstates.States.DEAD);
		boolean txShutdown		= (currentSendState		== org.bintrotter.apeer.sendstates.States.SHUTDOWN);
		boolean txConnecting	= (currentSendState		== org.bintrotter.apeer.sendstates.States.CONNECTING);
		boolean txHandshake		= (currentSendState		== org.bintrotter.apeer.sendstates.States.HANDSHAKE) |
		  						  (currentSendState		== org.bintrotter.apeer.sendstates.States.WAIT_FOR_HANDSHAKE);  
		boolean txProtocol		= !txDead && !txShutdown && !txHandshake && !txConnecting;
		
		
		if(txShutdown || rxShutdown)
		{
			if(!txShutdown && !txDead)
				currentSendState = org.bintrotter.apeer.sendstates.States.SHUTDOWN;

			if(!rxShutdown && !rxDead)
				currentReceiveState = org.bintrotter.apeer.receivestates.States.SHUTDOWN;
			
			return State.SHUTDOWN;
		}
		
		if(txProtocol && rxProtocol)
			return State.PROTOCOL;
		
		if(txHandshake || rxHandshake)
			return State.HANDSHAKE;
		
		if(txConnecting || rxConnecting)
			return State.CONNECTING;
		
		if(txDead && rxDead)
			return State.DEAD;
		
		// unknown state ?
		return null;
	}
}

